package com.zbkj.admin.boot.servlet.logger;

import com.zbkj.admin.boot.launch.MicaLogLevel;
import com.zbkj.admin.boot.props.MicaLogProperties;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.ClassUtil;
import net.dreamlu.mica.core.utils.JsonUtil;
import net.dreamlu.mica.core.utils.StringUtil;
import net.dreamlu.mica.core.utils.WebUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * Spring boot 控制器 请求日志，方便代码调试
 *
 * @author L.cm
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(value = MicaLogLevel.REQ_LOG_PROPS_PREFIX + ".enable", havingValue = "true", matchIfMissing = true)
public class RequestLogAspect {
    private final MicaLogProperties properties;

    /**
     * AOP 环切 控制器
     *
     * @param point JoinPoint
     * @return Object
     * @throws Throwable 异常
     */
    @Around(
            "execution(public !static net.dreamlu.mica.core.result.R *(..)) || " +
                    "execution(public !static com.zbkj.admin.result.CommonResult *(..)) && "
                    + "(@within(org.springframework.stereotype.Controller)"
                    + " || @within(org.springframework.web.bind.annotation.RestController) )"
                    + " && (@annotation(org.springframework.web.bind.annotation.RequestMapping)"
                    + " || @annotation(org.springframework.web.bind.annotation.GetMapping) "
                    + " || @annotation(org.springframework.web.bind.annotation.PostMapping) "
                    + " || @annotation(org.springframework.web.bind.annotation.PutMapping) "
                    + " || @annotation(org.springframework.web.bind.annotation.PatchMapping) "
                    + " || @annotation(org.springframework.web.bind.annotation.DeleteMapping))"
    )
    public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
        MicaLogLevel level = properties.getRequest().getLevel();
        // 不打印日志，直接返回
        if (MicaLogLevel.NONE == level) {
            return point.proceed();
        }
        HttpServletRequest request = WebUtil.getRequest();
        String requestUrl = request.getRequestURI();
        String requestMethod = request.getMethod();

        // 构建成一条长 日志，避免并发下日志错乱
        StringBuilder beforeReqLog = new StringBuilder(300);
        // 日志参数
        List<Object> beforeReqArgs = new ArrayList<>();
        beforeReqLog.append("\n\n================  Request Start  ================\n");
        // 打印路由
        beforeReqLog.append("===> {}: {}\n");
        beforeReqArgs.add(requestMethod);
        beforeReqArgs.add(requestUrl);
        // 打印请求参数
        logIngArgs(point, request, beforeReqLog, beforeReqArgs);
        // 打印请求 headers
        logIngHeaders(request, level, beforeReqLog, beforeReqArgs);
        beforeReqLog.append("================  Request End    ================\n");

        // 打印执行时间
        long startNs = System.nanoTime();
        log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
        // aop 执行后的日志
        StringBuilder afterReqLog = new StringBuilder(200);
        // 日志参数
        List<Object> afterReqArgs = new ArrayList<>();
        afterReqLog.append("\n\n================  Response Start  ================\n");
        try {
            Object result = point.proceed();
            // 打印返回结构体
            if (MicaLogLevel.BODY.lte(level)) {
                afterReqLog.append("===Result=======  {}\n");
                afterReqArgs.add(JsonUtil.toJson(result));
            }
            return result;
        } finally {
            String tookTime = StringUtil.format(System.nanoTime() - startNs);
            afterReqLog.append("<=== {}: {} ({})\n");
            afterReqArgs.add(requestMethod);
            afterReqArgs.add(requestUrl);
            afterReqArgs.add(tookTime);
            afterReqLog.append("================  Response End    ================\n");
            log.info(afterReqLog.toString(), afterReqArgs.toArray());
        }
    }

    /**
     * 激励请求参数
     *
     * @param point         ProceedingJoinPoint
     * @param request       HttpServletRequest
     * @param beforeReqLog  StringBuilder
     * @param beforeReqArgs beforeReqArgs
     */
    private static void logIngArgs(ProceedingJoinPoint point,
                                   HttpServletRequest request,
                                   StringBuilder beforeReqLog,
                                   List<Object> beforeReqArgs) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Map<String, String[]> paramMap = request.getParameterMap();
        paramMap.forEach((name, value) -> {
            beforeReqLog.append("===Parameters===  {}: {}\n");
            beforeReqArgs.add(name);
            beforeReqArgs.add(StringUtil.join(value));
        });
        // 一次请求只能有一个 request body
        Object requestBodyValue = null;
        for (int i = 0; i < args.length; i++) {
            // 读取方法参数
            MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
            RequestBody requestBody = methodParam.getParameterAnnotation(RequestBody.class);
            // 如果是body的json则是对象
            if (requestBody != null) {
                requestBodyValue = args[i];
                break;
            }
        }
        if (requestBodyValue != null) {
            beforeReqLog.append("===RequestBody==  {}\n");
            beforeReqArgs.add(JsonUtil.toJson(requestBodyValue));
        }
    }

    /**
     * 记录请求头
     *
     * @param request       HttpServletRequest
     * @param level         日志级别
     * @param beforeReqLog  StringBuilder
     * @param beforeReqArgs beforeReqArgs
     */
    private static void logIngHeaders(HttpServletRequest request,
                                      MicaLogLevel level,
                                      StringBuilder beforeReqLog,
                                      List<Object> beforeReqArgs) {
        // 打印请求头
        if (MicaLogLevel.HEADERS.lte(level)) {
            Enumeration<String> headers = request.getHeaderNames();
            while (headers.hasMoreElements()) {
                String headerName = headers.nextElement();
                String headerValue = request.getHeader(headerName);
                beforeReqLog.append("===Headers======  {}: {}\n");
                beforeReqArgs.add(headerName);
                beforeReqArgs.add(headerValue);
            }
        }
    }

}
